﻿//-----------------------------------------------------------------------
// <author>Paul Long</author>
//-----------------------------------------------------------------------

namespace NMCaptureData
{
    using System.Collections.Generic;
    using System.Globalization;
    using System.IO;
    using NetworkAddresses;
    using NMTopUsers;

    /// <summary>
    /// A NetworkElement is either a single machine or a pair of machines
    /// depending on how you started TopUsers.  This keeps track of the stats
    /// for each element, such as frames/bytes sent/received/total.
    /// </summary>
    internal class NetworkElement
    {
        #region States

        /// <summary>
        /// List of Network Elements that exist under an ethernet address.
        /// </summary>
        private List<NetworkElement> subNetworkElements;

        /// <summary>
        /// From and possibly to address of element.
        /// </summary>
        private Address fromAddress;

        /// <summary>
        /// To address if conversations are used.
        /// </summary>
        private Address toAddress;

        /// <summary>
        /// Total frames.
        /// </summary>
        private uint totalFrames;

        /// <summary>
        /// Received Frames
        /// </summary>
        private uint recvFrames;

        /// <summary>
        /// Sent frames
        /// </summary>
        private uint sentFrames;

        /// <summary>
        /// Total Bytes sent/received.
        /// </summary>
        private uint totalBytes;

        /// <summary>
        /// Received Bytes
        /// </summary>
        private uint recvBytes;

        /// <summary>
        /// Sent Bytes
        /// </summary>
        private uint sentBytes;

        /// <summary>
        /// Start Time of first received frame.
        /// </summary>
        private ulong recvStartTime;

        /// <summary>
        /// Start Time of first sent frame.
        /// </summary>
        private ulong sentStartTime;

        /// <summary>
        /// End Time of last received frame.
        /// </summary>
        private ulong recvEndTime;

        /// <summary>
        /// End Time of last sent frame.
        /// </summary>
        private ulong sentEndTime;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the NetworkElement class.
        /// Create a new Endpoint element and start collecting stats.  Direction is important
        /// to keep sent/received totals accurate.
        /// </summary>
        /// <param name="address">Address to add</param>
        /// <param name="dir">Direction of traffic</param>
        /// <param name="length">Length of frame</param>
        /// <param name="time">Time stamp of frame</param>
        public NetworkElement(Address address, Direction dir, uint length, ulong time)
        {
            this.fromAddress = address;

            if (dir == Direction.Forward)
            {
                this.sentStartTime = time;
                this.sentEndTime = time;

                this.totalFrames = 1;
                this.totalBytes = length;

                this.sentBytes = length;
                this.sentFrames = 1;

                this.recvBytes = 0;
                this.recvFrames = 0;
            }
            else
            {
                this.recvStartTime = time;
                this.recvEndTime = time;

                this.totalFrames = 1;
                this.totalBytes = length;

                this.recvBytes = length;
                this.recvFrames = 1;

                this.sentBytes = 0;
                this.sentFrames = 0;
            }

            this.subNetworkElements = new List<NetworkElement>();
        }

        /// <summary>
        /// Initializes a new instance of the NetworkElement class.
        /// Create a new Endpoint element and start collecting stats.  Direction is important
        /// to keep sent/received totals accurate.
        /// </summary>
        /// <param name="fromAddress">From address to add</param>
        /// <param name="toAddress">To address to add</param>
        /// <param name="dir">Direction of traffic</param>
        /// <param name="length">Length of frame</param>
        /// <param name="time">Time stamp of frame</param>
        public NetworkElement(Address fromAddress, Address toAddress, Direction dir, uint length, ulong time)
        {
            this.fromAddress = fromAddress;
            this.toAddress = toAddress;

            if (dir == Direction.Forward)
            {
                this.sentStartTime = time;
                this.sentEndTime = time;

                this.totalFrames = 1;
                this.totalBytes = length;

                this.sentBytes = length;
                this.sentFrames = 1;

                this.recvBytes = 0;
                this.recvFrames = 0;
            }
            else
            {
                this.recvStartTime = time;
                this.recvEndTime = time;

                this.totalFrames = 1;
                this.totalBytes = length;

                this.recvBytes = length;
                this.recvFrames = 1;

                this.sentBytes = 0;
                this.sentFrames = 0;
            }

            this.subNetworkElements = new List<NetworkElement>();
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets Sub Network Elements list.
        /// </summary>
        public List<NetworkElement> SubNetworkElements
        {
            get
            {
                return this.subNetworkElements;
            }
        }

        /// <summary>
        /// Gets Sending start time
        /// </summary>
        public ulong SentStartTime
        {
            get
            {
                return this.sentStartTime;
            }
        }

        /// <summary>
        /// Gets Received ending time
        /// </summary>
        public ulong ReceiveEndTime
        {
            get
            {
                return this.recvEndTime;
            }
        }

        /// <summary>
        /// Gets Total Bytes for endpoint or conversation
        /// </summary>
        public uint TotalBytes
        {
          get { return this.totalBytes; }
        }

      /// <summary>
      /// Received Bytes
      /// </summary>
      public uint RecvBytes
      {
        get { return recvBytes; }
      }

      /// <summary>
      /// Sent Bytes
      /// </summary>
      public uint SentBytes
      {
        get { return sentBytes; }
      }

      /// <summary>
      /// From and possibly to address of element.
      /// </summary>
      public Address FromAddress
      {
        get { return fromAddress; }
      }

      /// <summary>
      /// To address if conversations are used.
      /// </summary>
      public Address ToAddress
      {
        get { return toAddress; }
      }

      /// <summary>
      /// Total frames.
      /// </summary>
      public uint TotalFrames
      {
        get { return totalFrames; }
      }

      /// <summary>
      /// Received Frames
      /// </summary>
      public uint RecvFrames
      {
        get { return recvFrames; }
      }

      /// <summary>
      /// Sent frames
      /// </summary>
      public uint SentFrames
      {
        get { return sentFrames; }
      }

      /// <summary>
        /// Returns a row of data
        /// </summary>
        /// <param name="mode">Endpoint or Conversation</param>
        /// <param name="orderCount">Order inserted for tree</param>
        /// <param name="tree">If Tree is turned on.</param>
        /// <returns>An object array containing a row of data</returns>
        public object[] GetRow(TopUsers.OperationMode mode, uint orderCount, bool tree)
        {
            ulong sendDiff = this.sentEndTime - this.sentStartTime;
            ulong recvDiff = this.recvEndTime - this.recvStartTime;

            double sendKbps = CalcThroughput.Throughput(this.SentBytes, sendDiff);
            double recvKbps = CalcThroughput.Throughput(this.RecvBytes, recvDiff);

            string subname;
            if (tree)
            {
                subname = string.Format(CultureInfo.CurrentCulture, "  {0}", this.FromAddress.Display);
            }
            else
            {
                subname = string.Format(CultureInfo.CurrentCulture, "{0}", this.FromAddress.Display);
            }

            if (mode == TopUsers.OperationMode.Endpoint)
            {
                object[] row = 
                { 
                    subname, 
                    "blank", 
                    this.TotalBytes, 
                    (double) 0,
                    this.TotalFrames, 
                    this.SentBytes, 
                    (double) 0,
                    this.SentFrames, 
                    sendKbps, 
                    this.RecvBytes, 
                    (double) 0,
                    this.RecvFrames, 
                    recvKbps, 
                    sendDiff, 
                    orderCount 
                };

                return row;
            }
            else // Show conversations.
            {
                string subnameto;
                if (tree)
                {
                    subnameto = string.Format(CultureInfo.CurrentCulture, "  {0}", this.ToAddress.Display);
                }
                else
                {
                    subnameto = string.Format(CultureInfo.CurrentCulture, "{0}", this.ToAddress.Display);
                }

                object[] row = 
                {
                    subname, 
                    subnameto, 
                    this.TotalBytes, 
                    (double) 0,
                    this.TotalFrames, 
                    this.SentBytes, 
                    (double) 0,
                    this.SentFrames, 
                    sendKbps, 
                    this.RecvBytes, 
                    (double) 0,
                    this.RecvFrames, 
                    recvKbps, 
                    sendDiff, 
                    orderCount 
                };

                return row;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Dumps a single row.  For testing.
        /// </summary>
        /// <param name="fileStream">File to write to.</param>
        /// <param name="mode">Endpoint or Conversation mode</param>
        public void DumpRow(StreamWriter fileStream, TopUsers.OperationMode mode)
        {
            object[] row = this.GetRow(mode, 0, false);

            fileStream.WriteLine(
                                "{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13}",
                                this.DisplayAddress(mode),
                                row[2], row[3], row[4], row[5], row[6], row[7], row[8],
                                row[9], row[10], row[11], row[12], row[13], row[14]);
        }

        /// <summary>
        /// Displays the address depending on if this is Endpoint or Conversation mode.
        /// </summary>
        /// <param name="mode">Conversation or Endpoint</param>
        /// <returns>String representing the address or address pair.</returns>
        public string DisplayAddress(TopUsers.OperationMode mode)
        {
            if (mode == TopUsers.OperationMode.Endpoint)
            {
                return this.FromAddress.Display;
            }
            else
            {
                return string.Format(CultureInfo.CurrentCulture, "{0} - {1}", this.FromAddress.Display, this.ToAddress.Display);
            }
        }

        /// <summary>
        /// Looks for matching from address.
        /// </summary>
        /// <param name="from">Looks for matching address</param>
        /// <returns>True if match is found</returns>
        public bool MatchFromAddress(Address from)
        {
            return from == this.FromAddress;
        }

        /// <summary>
        ///  Looks for a matching to address.
        /// </summary>
        /// <param name="to">Looks for matching to address.</param>
        /// <returns>True if to address matches</returns>
        public bool MatchToAddress(Address to)
        {
            return to == this.ToAddress;
        }

        /// <summary>
        /// Checks if address is IPv4
        /// </summary>
        /// <returns>True if address is IPv4</returns>
        public bool IsIPv4Address()
        {
            return this.FromAddress is IPv4Address;
        }

        /// <summary>
        /// Checks if address is IPv6
        /// </summary>
        /// <returns>True if address is IPv6</returns>
        public bool IsIPv6Address()
        {
            return this.FromAddress is IPv6Address;
        }

        /// <summary>
        /// Checks if address is ethernet
        /// </summary>
        /// <returns>True if address is ethernet</returns>
        public bool IsEthernetAddress()
        {
            return this.FromAddress is MachineAddress;
        }

        /// <summary>
        /// Adds stats for a new frame.
        /// </summary>
        /// <param name="dir">Direction of traffic</param>
        /// <param name="length">Length of frame</param>
        /// <param name="time">Time stamp of frame</param>
        public void AddFrame(Direction dir, uint length, ulong time)
        {
            if (dir == Direction.Forward)
            {
                this.sentEndTime = time;

                if (this.sentStartTime == 0)
                {
                    this.sentStartTime = time;
                }

                this.totalFrames = this.TotalFrames + 1;
                this.totalBytes = this.TotalBytes + length;

                this.sentBytes = this.SentBytes + length;
                this.sentFrames = this.SentFrames + 1;
            }
            else
            {
                this.recvEndTime = time;

                if (this.recvStartTime == 0)
                {
                    this.recvStartTime = time;
                }

                this.totalFrames = this.TotalFrames + 1;
                this.totalBytes = this.TotalBytes + length;

                this.recvBytes = this.RecvBytes + length;
                this.recvFrames = this.RecvFrames + 1;
            }
        }

        /// <summary>
        /// Looks for a matching Endpoint IPv4 or IPv6 address in the list for this Ethernet Address.
        /// </summary>
        /// <param name="ip">IP address to search for</param>
        /// <returns>NewtorkElement of found address or null if not found</returns>
        public NetworkElement FindNetworkElementByIP(Address ip)
        {
            NetworkElement nm = null;

            if (this.SubNetworkElements != null)
            {
                nm = this.SubNetworkElements.Find(delegate(NetworkElement m) { return m.FromAddress == ip; });
            }

            return nm;
        }

        /// <summary>
        /// Looks for a matching Endpoint IPv4 or IPv6 address in the list for this Ethernet Address.
        /// </summary>
        /// <param name="from">From address to search for</param>
        /// <param name="to">To address to search for</param>
        /// <returns>NewtorkElement of found address or null if not found</returns>
        public NetworkElement FindNetworElementByIPPair(Address from, Address to)
        {
            NetworkElement nm = null;

            if (this.SubNetworkElements != null)
            {
                nm = this.SubNetworkElements.Find(
                            delegate(NetworkElement m)
                            {
                                return (m.MatchFromAddress(from) && m.MatchToAddress(to));
                            });
            }

            return nm;
        }

        /// <summary>
        /// Addes a sub Network Element to an Ethernet address.
        /// </summary>
        /// <param name="ip">Network Element to add</param>
        /// <param name="dir">Direction of network traffic</param>
        /// <param name="length">Length of frame</param>
        /// <param name="time">Time stamp of frame</param>
        public void AddNetworkElementIP(Address ip, Direction dir, uint length, ulong time)
        {
            NetworkElement nm = new NetworkElement(ip, dir, length, time);
            this.SubNetworkElements.Add(nm);
        }

        #endregion
    }
}
